js 事件流

js 事件流是指事件在 dom 中流转的顺序(或者传播路径),事件流转分为三个过程,捕获阶段 - 目标阶段 - 冒泡阶段。下面我们通过实践探索下事件流机制。

实践

首先用 html 和 css 实现如下效果

image.png


接着我们通过 js 代码来控制事件回调执行的时机
  • 回调在冒泡阶段执行
1
2
3
4
5
6
7
function log(msg) {
console.log(msg);
}
// 回调冒泡阶段执行
document.querySelector('.wrapper').addEventListener('click', () => log('wraper'), false);
document.querySelector('.outer').addEventListener('click', () => log('outer'), false);
document.querySelector('.inner').addEventListener('click', () => log('inner'), false);

我们分别点击wrapper、outer、inner,查看打印结果

点击对象 打印结果
wrapper wrapper
outer outer、wrapper
inner inner、outer、wrapper

从打印结果我们可以得出如下结论:
当我们点击某个节点时,首先打印该节点的内容,然后,从该节点开始向上冒泡,若父级监听了相同的事件, 那么父级对应的事件回调也会执行,直到 window。

  • 回调在捕获阶段执行

接着我们把上面 js 代码 addEventListener 的第三个参数改为 true,让回调在捕获阶段执行,

1
2
3
4
5
6
7
function log(msg) {
console.log(msg);
}
// 回调捕获阶段执行
document.querySelector('.wrapper').addEventListener('click', () => log('wraper'), true);
document.querySelector('.outer').addEventListener('click', () => log('outer'), true);
document.querySelector('.inner').addEventListener('click', () => log('inner'), true);

然后再次分别点击wrapper、outer、inner,查看打印结果

点击对象 打印结果
wrapper wrapper
outer wrapper、outer
inner wrapper、outer、inner

从这次的打印结果,我们可以得出结论:
当某个节点被点击时,事件流从顶级 window 依次向下传播,若传播到的节点也监听了相同的事件,那么该事件的回调也会执行,直到该点击节点。

  • 混合

下面我们看下更复杂的情况

1
2
3
4
5
6
7
8
function log(msg) {
console.log(msg);
}

document.querySelector('.wrapper').addEventListener('click', () => log('wraper one'), true);
document.querySelector('.wrapper').addEventListener('click', () => log('wraper two'), false);
document.querySelector('.outer').addEventListener('click', () => log('outer'), false);
document.querySelector('.inner').addEventListener('click', () => log('inner'), true);

依次点击wrapper、outer、inner, 看下打印结果

点击对象 打印结果
wrapper wrapper one、wrapper two
outer wrapper one、outer、wrapper two
inner wrapper one、inner、outer、wrapper two

由这次的打印结果及之前的结论,我们可以得出最终的结论。

总结:

事件回调的执行顺序是按照事件流来的传播顺序执行的,首先是捕获阶段,若从window 到 当前点击节点的有捕获阶段的监听函数,则按照从顶级 window 到当前点击节点的传播顺序执行回调,接着到达目标阶段,即执行当前点击节点的回调,最后是冒泡阶段,若从当前节点到顶级 window 有冒泡阶段的监听函数,则按照这个传播顺序执行回调。

注: 事件的监听节点可以看做是目标阶段,若是一个节点既在捕获阶段又在冒泡阶段监听了相同的事件,那么这个节点两个事件回调的执行顺序按照事件的添加的顺序执行(不区分捕获阶段和冒泡阶段,同属于目标阶段)。